
ENT.Type = "anim"
ENT.Base = "base_gmodentity"

ENT.PrintName = "Router"
ENT.Author = "[GU]|ZLC|thomasfn"
ENT.Contact = "Don't."
ENT.Instructions = "Spawn it. Then link things to it to use it."
ENT.Purpose = ""

ENT.Spawnable = false
ENT.AdminSpawnable = false

ENT.ItemModel = "models/props_lab/reciever01a.mdl"
util.PrecacheModel( ENT.ItemModel )

ENT.IsRouter = true
ENT.SatRouter = false

if (SERVER) then

	AddCSLuaFile( "shared.lua" )
	
	ENT.NextUse = CurTime()

	function ENT:Initialize()
		self.Entity:SetModel(self.ItemModel)
		self.Entity:PhysicsInit(SOLID_VPHYSICS)
		self.Entity:SetMoveType(MOVETYPE_VPHYSICS)
		self.Entity:SetSolid(SOLID_VPHYSICS)
		
		if (!self.Data) then self.Data = {} end
		if (!self.Data[self:EntIndex()]) then self.Data[self:EntIndex()] = {} end
		
		local id = self:FindRouterSpot()
		self.Data[self:EntIndex()].RID = id
		self:UpdateInt( "Subnet", id-1 )
		PCMod.Routers[ id ] = self
		
		self.Data[self:EntIndex()].R_PCS = {}
		self.Data[self:EntIndex()].R_RS = {}
		self.Data[self:EntIndex()].R_BIPS = {}
		
		if (!self.Password) then self.Password = "" end
		
		local phys = self.Entity:GetPhysicsObject()
		if (phys && phys:IsValid()) then phys:Wake() end
		
		PCBeam:BeamVarsToAll( self )
	end
	
	function ENT:OnRemove()
		// Clean up
		PCMod.Routers[ self.Data[self:EntIndex()].RID ] = nil
	end
	
	function ENT:MakeWireless()
		self.SatRouter = true
		PCBeam:SetValue( self, "IsWireless", true )
		PCBeam:BeamVarsToAll( self )
		self:ChangeModel( "models/props_lab/reciever01b.mdl" )
	end
	
	function ENT:ChangeModel( model )
		self.ItemModel = model
		
		self.Entity:SetModel(self.ItemModel)
		self.Entity:PhysicsInit(SOLID_VPHYSICS)
		self.Entity:SetMoveType(MOVETYPE_VPHYSICS)
		self.Entity:SetSolid(SOLID_VPHYSICS)
	end
	
	function ENT:FindRouterSpot()
		if (!PCMod) then PCMod = {} end
		if (!PCMod.Routers) then PCMod.Routers = {} end
		for i=1,255 do
			if ((!PCMod.Routers[i]) || (!PCMod.Routers[i]:IsValid())) then return i end
		end
	end
	
	function ENT:RegisterPC( pc, hint )
		if (!hint) then hint = 1 end
		if (!pc) then return "" end
		ip = self:GenerateIP( hint )
		table.insert( self.Data[self:EntIndex()].R_PCS, { pc, ip } )
		return ip
	end
	
	function ENT:UnregisterPC( pc )
		if (!pc) then return end
		for k, v in pairs( self.Data[self:EntIndex()].R_PCS ) do
			if (v[1] == pc) then
				table.remove( self.Data[self:EntIndex()].R_PCS, k )
				return
			end
		end
	end
	
	function ENT:RegisterRouter( router )
		table.insert( self.Data[self:EntIndex()].R_RS, { router } )
	end
	
	function ENT:HasRouter( router )
		for i=1,#self.Data[self:EntIndex()].R_RS do
			obj = self.Data[self:EntIndex()].R_RS[i]
			if (obj[1] == router) then return true end
		end
	end
	
	function ENT:GenerateIP( hint )
		// Check that the hint is availible
		if (!hint) then hint = "1" end
		local ip_c = self.Data[self:EntIndex()].RID-1
		local router_prefix = "192.168." .. tostring(ip_c) .. "."
		local ha = (!self:HasIP( router_prefix .. tostring(hint) ))
		if (ha) then
			// If so, give them the hint'd IP
			return router_prefix .. tostring(hint)
		else
			// If not, find a spare IP
			for i=1,255 do
				local spare = (!self:HasIP( router_prefix .. tostring(i) ))
				if (spare) then return router_prefix .. tostring(#self.Data[self:EntIndex()].R_PCS+1) end
			end
			// Theres no more slots left on the router!
			// TODO: Code for this rare event
		end
	end
	
	function ENT:HasIP( IP )
		for i=1, #self.Data[self:EntIndex()].R_PCS do
			if (self.Data[self:EntIndex()].R_PCS[i][2] == IP) then return true end
		end
	end
	
	function ENT:IPToENT( IP )
		for i=1, #self.Data[self:EntIndex()].R_PCS do
			if (self.Data[self:EntIndex()].R_PCS[i][2] == IP) then return self.Data[self:EntIndex()].R_PCS[i][1] end
		end
	end

	function ENT:LookForIP( IP, filter )
		// Notes:
		// This function should only return true IF this entity can find a path to the target.
		// Filter is a table of all entities that can definetly NOT find the target IP. It gets updated on the way through.
		// If this function returns true, a packet will be sent to this entity.
		// Also, if this function returns true, the next path node (the entity which discovered the IP that is linked to this one) is stored in memory.
		// This function will check the entity's memory first to see if a path has previously been found
		
		// Make sure our memory is defined
		if (!self.Data[ self:EntIndex() ].Memory) then self.Data[ self:EntIndex() ].Memory = {} end
		
		// See if the next path node is in memory already
		local mem = self.Data[ self:EntIndex() ].Memory[ip]
		if (mem) then
			// The next path node IS in memory! That means we have a path!
			return true
		end
		
		// If needed, start off the filter
		if (!filter) then filter = {} end
		
		// If we are in the filter, we shouldn't have got this far but lets check anyway
		if (tblHasValue( filter, self )) then
			// Something has got mislead - lets return false to be safe
			return false
		end
		
		// Add us to the filter (if we do happen to find the IP, we will be returning true anyway)
		table.insert( filter, self )
		
		// Check if we have the target IP registered
		if (self:HasIP( IP )) then
			// Add ourself as a path node and return true
			table.insert( PacketPath, self )
			return true
		end
		
		// Build a table of entities to check
		local check = {}
		table.Add( check, self.Data[self:EntIndex()].R_RS )
		
		// If we are wireless, add all other wireless routers
		if (self.SatRouter) then
			for k, v in pairs( ents.FindByClass( "pcmod_router" ) ) do
				if v.SatRouter then
					table.insert( check, { v } )
				end
			end
		end
		
		// Go through our list of routers and check if they have the IP
		for i=1,#check do
			// Get the object
			obj = check[i]
			
			// Make sure it is not on the filter
			if (!tblHasValue( filter, obj[1] )) then
				// See if the object can find the target
				local res = obj[1]:LookForIP( IP, filter, self )
				
				// If not, add it to the filter
				if (!res) then
					table.insert( filter, obj[1] )
				else
					// If so, this router CAN find a path - add it to our temporary memory
					self.Data[ self:EntIndex() ].Memory[ IP ] = obj[1]
					
					// Add ourself as a path node and return true
					table.insert( PacketPath, self )
					return true
				end
			end
		end
		
		// Nothing found, return false
		return false
	end
	
	function tblHasValue( tbl, value )
		local i = 0
		for i=1,#tbl do
			if (tbl[i] == value) then return true end
		end
		return false
	end
	
	// DeliveryCode:
	// 0 = Cannot Deliver
	// 1 = Delivered
	// 2 = Destination not found
	// 3 = No Network
	// 4 = Packet Denied: Closed Port
	// 5 = Packet Denied: Blocked IP
	// 6 = Packet Denied: Invalid packet
	
	function ENT:DeliverPacket( Packet )
		// Ensure the packet is valid
		if (!Packet) then return 6 end
		if (#Packet<4) then return 6 end
		
		// Get the target and source IPs
		local target = Packet[2]
		local source = Packet[1]
		
		// If the target is the router (us), then let us deal with the packet and stop
		if (target == "ROUTER") then return self:DoPacket( Packet ) end
		
		// If the source IP is blocked, stop here
		if (self:IPBlocked(source)) then return 5 end
		
		// Check if we have the target IP registered
		if (self:HasIP( target )) then
			local obj = self:IPToENT( target )
			if (!obj) then
				// TODO: Remove the false entity
				return 2
			end
			return obj:PacketDelivered( Packet )
		end
		
		// See if theres a path already planned for us
		if (#PacketPath > 1) then
			// Get the next node, and remove it from the path
			local ent = PacketPath[ #PacketPath-1 ]
			table.remove( PacketPath )
			// Pass the packet onto our next path node
			return ent:DeliverPacket( Packet, self )
		end
		
		// There is no path, build one
		local result = self:LookForIP( target, nil, self )
		
		// NOW See if theres a path planned for us
		if (#PacketPath > 1) then
			// Get the next node, and remove it from the path
			local ent = PacketPath[ #PacketPath-1 ]
			table.remove( PacketPath )
			// Pass the packet onto our next path node
			return ent:DeliverPacket( Packet, self )
		end
		
		// Still no path, we can't deliver - clean the path
		return 2
	end
	
	function ENT:DoPacket( Packet )
		// We have recieved a packet flagged as a ROUTER packet. Deal with it AND return a deliv code.
		if (!Packet) then return 6 end
		local target = Packet[2]
		if (!target) then return 6 end
		if (target != "ROUTER") then return 0 end
		local data = Packet[4]
		if (!data) then return 6 end
		
		// Ports arn't important here. From here, we return 1. (Unless incorrect password) Check the password.
		if (Packet[5] != self.Password) then return 0 end
		
		// Correct authentification, now see what we have to do
		if (data[2] == "block_ip") then
			local ip = data[3]
			self:BlockIP( ip )
		end
		if (data[2] == "unblock_ip") then
			local ip = data[3]
			self:UnblockIP( ip )
		end
	end
	
	function ENT:Use( activator, ply )
		if (CurTime() < self.NextUse) then return end
		self.NextUse = CurTime() + 1
		ply:PrintMessage( HUD_PRINTTALK, "Blocked IP List" )
		for i=1,#self.Data[self:EntIndex()].R_BIPS do
			obj = self.Data[self:EntIndex()].R_BIPS[i]
			ply:PrintMessage( HUD_PRINTTALK, obj )
		end
	end
	
	function ENT:Think()
		// Several things to do here.
		// First: Check that all the registered PCs are valid
		for i=1,#self.Data[self:EntIndex()].R_PCS do
			local obj = self.Data[self:EntIndex()].R_PCS[i]
			if (!obj) then
				// Table entry is nil
				table.remove( self.Data[self:EntIndex()].R_PCS, i )
			else
				if ((!obj[1]) || (!obj[1]:IsValid())) then
					// PC object is nil
					table.remove( self.Data[self:EntIndex()].R_PCS, i )
				else
					if (obj[1].IsPC) then
						local status = obj[1].Data[ obj[1]:EntIndex() ].Status
						if ((status != 3) && (status != 2)) then
							// PC is not on
							table.remove( self.Data[self:EntIndex()].R_PCS, i )
						end
					end
					if (obj[1].IsServer) then
						local status = obj[1].Data[ obj[1]:EntIndex() ].IsOn
						if (!status) then
							// Server is not on
							table.remove( self.Data[self:EntIndex()].R_PCS, i )
						end
					end
				end
			end
		end
		
		// Second: Check all the registered routers are valid
		for i=1,#self.Data[self:EntIndex()].R_RS do
			obj = self.Data[self:EntIndex()].R_RS[i]
			if (!obj) then
				table.remove( self.Data[self:EntIndex()].R_RS, i )
			else
				if (!obj[1]) then
					table.remove( self.Data[self:EntIndex()].R_RS, i )
				end
			end
		end
		
		// Third: Clean out our memory
		self.Data[ self:EntIndex() ].Memory = {}
	end
	
	function ENT:UpdateInt( var, value )
		self.Data[self:EntIndex()][var] = value
		PCBeam:SetValue( self, var, value )
	end
	
	function ENT:UpdateBool( var, value )
		self.Data[self:EntIndex()][var] = value
		PCBeam:SetValue( self, var, value )
	end
	
	function ENT:UpdateStr( var, value )
		self.Data[self:EntIndex()][var] = value
		PCBeam:SetValue( self, var, value )
	end
	
	function ENT:BlockIP( IP )
		if (self:IPBlocked( IP )) then return end
		table.insert(self.Data[self:EntIndex()].R_BIPS, IP)
	end
	
	function ENT:UnblockIP( IP )
		if (!self:IPBlocked( IP )) then return end
		for i=1,#self.Data[self:EntIndex()].R_BIPS do
			obj = self.Data[self:EntIndex()].R_BIPS[i]
			if (obj == IP) then
				table.remove(self.Data[self:EntIndex()].R_BIPS, i)
				return
			end
		end
	end
	
	function ENT:IPBlocked( IP )
		for i=1,#self.Data[self:EntIndex()].R_BIPS do
			obj = self.Data[self:EntIndex()].R_BIPS[i]
			if (obj == IP) then return true	end
		end
	end

end

if (CLIENT) then

	function ENT:Think()
		self.SatRouter = PCBeam:GetBeamValue( self, "IsWireless", false )
	end

	function ENT:Draw()
		// Draw our entity
		self.BaseClass.Draw( self )
	end	
end